package discover

import (
	"common/config"
	"common/etcdcli"
	"common/logs"
	"context"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
	"google.golang.org/grpc/attributes"
	"google.golang.org/grpc/resolver"
)

type Resolver struct {
	conf        config.EtcdConf
	cc          resolver.ClientConn
	etcdCli     *clientv3.Client // etcd链接
	closeCh     chan struct{}
	Key         string
	srvAddrList []resolver.Address
	watchCh     clientv3.WatchChan
}

// Build 当grpc。Dail的时候会调用这个方法
func (r *Resolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r.cc = cc
	// 1. 建立链接
	var err error
	r.etcdCli, err = etcdcli.GetInstance(r.conf)
	if err != nil {
		logs.Error("grpc client  connect etcd err ：%v", err)
	}

	r.closeCh = make(chan struct{})
	// 2. 根据key获取value
	logs.Info("target.URL.Path", target.URL.Path)
	r.Key = target.URL.Path
	if err := r.sync(); err != nil {
		return nil, err
	}
	// 3. 如果节点有变动了，事实更新信息
	go r.watch()
	return nil, nil
}

func (r *Resolver) sync() error {
	ctx, cancel := context.WithTimeout(context.TODO(), time.Duration(r.conf.RWTimeout)*time.Second)
	defer cancel()

	res, err := r.etcdCli.Get(ctx, r.Key, clientv3.WithPrefix())
	if err != nil {
		logs.Error("grpc client get etcd failed,name=%s,err:%v", r.Key, err)
	}
	logs.Info("%v", res.Kvs)
	r.srvAddrList = []resolver.Address{}
	for _, v := range res.Kvs {
		server, err := ParseValue(v.Value)
		if err != nil {
			logs.Error("grpc client parse etcd value failed,name=%s,err:%v", v.Key, err)
			continue
		}
		r.srvAddrList = append(r.srvAddrList, resolver.Address{
			Addr:       server.Addr,
			Attributes: attributes.New("weight", server.Weight),
		})
	}
	if len(r.srvAddrList) == 0 {
		logs.Error("not found services")
		return nil
	}
	err = r.cc.UpdateState(resolver.State{
		Addresses: r.srvAddrList,
	})
	if err != nil {
		logs.Error("grpc client updateState failed,name=%s,err:%v", r.Key, err)
		return err
	}
	return nil
}
func (r *Resolver) watch() {
	r.watchCh = r.etcdCli.Watch(context.Background(), r.Key, clientv3.WithPrefix())
	ticker := time.NewTicker(time.Minute)
	for {
		select {
		case <-r.closeCh:
			r.Close()
		case <-ticker.C:
			if err := r.sync(); err != nil {
				logs.Error("watch sync failed, err:%v", err)
			}
		case res, ok := <-r.watchCh:
			if ok {
				r.update(res.Events)
			}
		}

	}

}

func (r Resolver) Scheme() string {
	return "etcd"
}
func (r Resolver) update(events []*clientv3.Event) {
	for _, ev := range events {
		switch ev.Type {
		case clientv3.EventTypePut:
			server, err := ParseValue(ev.Kv.Value)
			if err != nil {
				logs.Error("grpc client update(eventtypePut) parse etcd value failed,name=%s,err=:%v", r.Key, err)
			}
			addr := resolver.Address{
				Addr:       server.Addr,
				Attributes: attributes.New("weight", server.Weight),
			}
			if !Exist(r.srvAddrList, addr) {
				r.srvAddrList = append(r.srvAddrList, addr)
				err = r.cc.UpdateState(resolver.State{
					Addresses: r.srvAddrList,
				})
				if err != nil {
					logs.Error("grpc client update(EventTypePut) updateState failed,name=%s,err=%v", r.Key, err)
				}
			}
		case clientv3.EventTypeDelete:
			server, err := ParseKey(string(ev.Kv.Key))
			if err != nil {
				logs.Error("grpc client updatte(EventTypeDelete) parse key failed:name=%s,err=%v", r.Key, err)
			}
			addr := resolver.Address{Addr: server.Addr}
			if list, ok := Remove(r.srvAddrList, addr); ok {
				r.srvAddrList = list
				err = r.cc.UpdateState(resolver.State{
					Addresses: r.srvAddrList,
				})
				if err != nil {
					logs.Error("grpc client update(EventTypeDelete) updateState failed,name=%s,err=%v", r.Key, err)
				}
			}

		}
	}
}
func (r Resolver) Close() {
	if r.etcdCli != nil {
		err := r.etcdCli.Close()
		if err != nil {
			logs.Error("Resolver close etcd err :%v", err)
		}
	}
}

func Exist(list []resolver.Address, addr resolver.Address) bool {
	for _, v := range list {
		if v.Addr == addr.Addr {
			return true
		}
	}
	return false
}
func Remove(list []resolver.Address, addr resolver.Address) ([]resolver.Address, bool) {
	for i := range list {
		if list[i].Addr == addr.Addr {
			list[i] = list[len(list)-1]
			return list[:len(list)-1], true
		}
	}
	return nil, false
}
func NewResolver(conf config.EtcdConf) *Resolver {

	return &Resolver{
		conf: conf,
	}
}
